use std::{
    sync::{atomic::*, mpsc::Sender, LazyLock, Mutex}, thread::{self, JoinHandle}, time::{Duration,Instant},
};

use crate::{
    defines::*, log, config::{self, Config}, utils::{self, lock_free_dt::IndexedList},
    game, engine::{self, ReplyMessage, TakeMessage},
};

const MESSAGE_SPLIT_0:char = ' ';
const MESSAGE_SPLIT_1:char = '|';

const MESSAGE_HEAD_HEARTBEAT:&str = "heartbeat";
const MESSAGE_HEAD_SETI:&str = "set-i";
const MESSAGE_HEAD_SETB:&str = "set-b";
const MESSAGE_HEAD_STEP:&str = "step";
const MESSAGE_HEAD_QUIT:&str = "-q";

const MESSAGE_SET_I_KIND_UCT_TIMES:&str = "uct-times";
const MESSAGE_SET_I_KIND_LEVEL:&str = "level";

const MESSAGE_SET_B_KIND_BG_THINK:&str = "bg-think";
const MESSAGE_SET_B_YES:&str = "yes";
const MESSAGE_SET_B_NO:&str = "no";

struct Instance {
    tx: Sender<ProtocolIO>,
    time_htake: Instant,
    time_hreply: Instant,
}
struct Shared {
    instances: IndexedList<Instance>,
    running: AtomicBool,
    main_handle: Mutex<Option<JoinHandle<()>>>,
    cfg: IndexedList<config::Protocol>,
}
impl Shared {
    #[inline] fn cfg(&self) -> &'static config::Protocol {
        unsafe {&*(self.cfg.get(0).unwrap())}
    }
    fn cfg_init(&self, a:config::Protocol) {
        let _ = self.cfg.set(0, a);
    }
}
static SHARED:LazyLock<Shared> = LazyLock::new(||{
    Shared {
        instances: IndexedList::new(CLIENT_MAX_NUMS),
        running: AtomicBool::new(false),
        main_handle: Mutex::new(None),
        cfg: IndexedList::new(1),
    }
});

#[derive(Debug)]
pub enum ProtocolIO {
    Reply(String),
    Close(String),
    Quit,
}

fn push_server(cid:ClientId, msg:ProtocolIO) {
    debug_assert!(cid < CLIENT_MAX_NUMS);
    if let Some(slot) = SHARED.instances.get_mut(cid) {
        let _ = slot.tx.send(msg);
    }
}
pub fn instance_is_alive(cid:ClientId) -> bool {
    debug_assert!(cid < CLIENT_MAX_NUMS);
    let slot = SHARED.instances.get(cid);
    if let Some(ptr) = slot {
        ! ptr.is_null()
    } else {
        false
    }
}
fn instance_get_mut(cid:ClientId) -> &'static mut Instance {
    debug_assert!(instance_is_alive(cid));
    SHARED.instances.get_mut(cid).unwrap()
}

fn protocol_main() {
    log::info!("[protocol] start");
    const REPLY_OVERTIME:Duration = Duration::from_millis(PROTOCOL_HREPLY_TIMEOUT_MS as u64);
    const TAKE_OVERTIME:Duration = Duration::from_millis(PROTOCOL_HTAKE_TIMEOUT_MS as u64);
    fn poll_engine_message() {
        if let Some(msg) = engine::poll() {
            match msg {
                ReplyMessage::Report(cid, a) => {
                    push_server(cid, ProtocolIO::Reply(reply::report(&a)));
                }
                ReplyMessage::Say(cid, a) => {
                    push_server(cid, ProtocolIO::Reply(reply::say(&a)));
                }
                ReplyMessage::Move(cid, loc) => {
                    push_server(cid, ProtocolIO::Reply(reply::down(loc)));
                }
                ReplyMessage::Pass(cid) => {
                    push_server(cid, ProtocolIO::Reply(reply::pass()));
                }
                ReplyMessage::Debug(cid, a) => {
                    push_server(cid, ProtocolIO::Reply(reply::debug(&a)));
                }
                ReplyMessage::Ready(cid, a) => {
                    push_server(cid, ProtocolIO::Reply(reply::ready(&a)));
                }
                _ => {}
            }
        }
    }
    let mut frame_sync = utils::FrameSync::with_ms(PROTOCOL_MAIN_MS);
    while SHARED.running.load(Ordering::Relaxed) && engine::is_running() {
        poll_engine_message();
        for cid in 0..CLIENT_MAX_NUMS {
            if instance_is_alive(cid) {
                if engine::instance_is_alive(cid) {
                    let pi = instance_get_mut(cid);
                    if SHARED.cfg().check_client_heartbeat && pi.time_htake.elapsed() >= TAKE_OVERTIME {
                        push_server(cid, ProtocolIO::Close(reply::error("heartbeat overtime")));
                        log::warning!("[protocol|client:{}] client heartbeat overtime", cid);
                    } else if SHARED.cfg().send_heartbeat && pi.time_hreply.elapsed() >= REPLY_OVERTIME {
                        pi.time_hreply = Instant::now();
                        push_server(cid, ProtocolIO::Reply(reply::heartbeat()));
                    }
                } else {
                    let e = anyhow!("engine instance end before protocol instance");
                    push_server(cid, ProtocolIO::Reply(reply::error(&format!("{}", e))));
                    free(cid);
                    log::error!("[protocol|client:{}] {}", cid, e);
                }
            }
        }
        frame_sync.sync();
    }
    log::info!("[protocol] end");
}

pub fn _init(config:&Config) -> Result<()> {
    debug_assert!(SHARED.main_handle.lock().unwrap().is_none());
    match SHARED.main_handle.lock() {
        Ok(mut mh) => {
            SHARED.cfg_init(config.protocol.clone());
            SHARED.running.store(true, Ordering::Relaxed);
            let handle = thread::spawn(protocol_main);
            mh.replace(handle);
            Ok(())
        }
        _ => {Err(anyhow!("[protocol] can't initialize main-thread"))}
    }
}

pub fn _raii() {
    let mut mh = SHARED.main_handle.lock().unwrap();
    if let Some(handle) = mh.take() {
        end();
        if let Err(_) = handle.join() {
            log::warning!("[protocal] main thread incorrect fuck")
        }
    }
    for i in 0..CLIENT_MAX_NUMS {
        free(i);
    }
}

pub mod reply {
    use super::*;
    #[inline] pub fn heartbeat() -> String {MESSAGE_HEAD_HEARTBEAT.into()}
    #[inline] pub fn down(loc:game::Loc) -> String{format!("move {}", loc)}
    #[inline] pub fn pass() -> String {format!("pass")}
    #[inline] pub fn report(a:&str) -> String {format!("report {}", a)}
    #[inline] pub fn say(a:&str) -> String {format!("say {}", a)}
    #[inline] pub fn debug(a:&str) -> String {format!("debug {}", a)}
    #[inline] pub fn ready(a:&str) -> String {format!("ready {}", a)}
    #[inline] pub fn error(a:&str) -> String {format!("error {}", a)}
}

fn on_message_seti(cid:ClientId, args:&[&str]) -> Result<()> {
    if args.len() > 1 {
        let name = args[0].to_ascii_lowercase();
        let kind = match name.as_str() {
            MESSAGE_SET_I_KIND_UCT_TIMES => {engine::ParamIntKind::UctTimes}
            MESSAGE_SET_I_KIND_LEVEL => {engine::ParamIntKind::Level}
            _ => {Err(anyhow!("{} kind '{}' not supported", MESSAGE_HEAD_SETI, name))?}
        };
        let a = if let Ok(n) = args[1].parse::<isize>() {n} else {
            Err(anyhow!("{} value '{}' incorrect", MESSAGE_HEAD_SETI, args[1]))?
        };
        engine::send(TakeMessage::SetI(cid, kind, a));
        Ok(())
    } else {
        Err(anyhow!("{} must have 2 argument", MESSAGE_HEAD_SETI))
    }
}
fn on_message_setb(cid:ClientId, args:&[&str]) -> Result<()> {
    if args.len() > 1 {
        let name = args[0].to_ascii_lowercase();
        let kind = match name.as_str() {
            MESSAGE_SET_B_KIND_BG_THINK => {engine::ParamBoolKind::AllowBackgroundThink}
            _ => {Err(anyhow!("{} kind '{}' not supported", MESSAGE_HEAD_SETB, name))?}
        };
        let a = match args[1] {
            MESSAGE_SET_B_YES => {true}
            MESSAGE_SET_B_NO => {false}
            _ => {
                Err(anyhow!("{} value must be '{}' or '{}", MESSAGE_HEAD_SETB, MESSAGE_SET_B_YES, MESSAGE_SET_B_NO))?
            }
        };
        engine::send(TakeMessage::SetB(cid, kind, a));
        Ok(())
    } else {
        Err(anyhow!("{} must have 2 argument", MESSAGE_HEAD_SETB))
    }
}
fn parse_piece_str(a:&str) -> game::Piece {
    match &a[0..1] {
        "b" | "B" => {game::Piece::Black}
        "w" | "W" => {game::Piece::White}
        _ => {game::Piece::Empty}
    }
}
fn parse_pcur(a:&str) -> Result<game::Piece> {
    match parse_piece_str(a) {
        game::Piece::Empty => {Err(anyhow!("{} pcur must be 'b' or 'w'", MESSAGE_HEAD_STEP))}
        p@_ => {Ok(p)}
    }
}
fn parse_board(a:&str) -> Result<game::Board> {
    fn set_board(board:&mut game::Board, loc:game::Loc, p:game::Piece, n:usize) -> game::Loc {
        if n > 0 {
            board.set(loc, p);
            set_board(board, 1+loc, p, n-1)
        } else {loc}
    }
    let mut board = game::Board::default();
    let mut i = 0;
    let b:Vec<&str> = a.split(MESSAGE_SPLIT_1).collect();
    for part in b {
        let p = parse_piece_str(part);
        let n = usize::from_str_radix(&part[1..], 10).unwrap_or(0);
        i = set_board(&mut board, i, p, n);
    }
    Ok(board)
}
fn on_message_step(cid:ClientId, args:&[&str]) -> Result<()> {
    if args.len() > 1 {
        let pcur = parse_pcur(args[0])?;
        let board = parse_board(args[1])?;
        engine::send(TakeMessage::Step(cid, pcur, board));
        Ok(())
    } else {
        Err(anyhow!("{} must have 2 argument", MESSAGE_HEAD_STEP))
    }
}
#[inline] fn on_message_heartbeat(cid:ClientId) {
    if instance_is_alive(cid) {
        instance_get_mut(cid).time_htake = Instant::now();
    }
}
pub fn do_message(cid:ClientId, text:&str) -> Result<()> {
    #[inline ] fn sub_command<F:Fn(ClientId,&[&str])->Result<()>>(units:&[&str], cid:ClientId, f:F) -> Result<()> {
        if let Err(e) = f(cid, &units[1..]) {
            push_server(cid, ProtocolIO::Reply(reply::error(&format!("{}", e))));
            Err(e)
        } else {
            Ok(())
        }
    }
    let units:Vec<&str> = text.split_terminator(MESSAGE_SPLIT_0).collect();
    if units.is_empty() {
        Ok(())
    } else {
        let mut to_heartbeat = false;
        let head = units[0].to_ascii_lowercase();
        match head.as_str() {
            MESSAGE_HEAD_SETI => {sub_command(&units, cid, on_message_seti)?;}
            MESSAGE_HEAD_SETB => {sub_command(&units, cid, on_message_setb)?;}
            MESSAGE_HEAD_STEP => {sub_command(&units, cid, on_message_step)?;}
            MESSAGE_HEAD_HEARTBEAT => {
                to_heartbeat = true;
            }
            MESSAGE_HEAD_QUIT => {
                push_server(cid, ProtocolIO::Quit);
            }
            _ => {
                if SHARED.cfg().allow_client_any_heartbear {
                    to_heartbeat = true;
                }
                push_server(cid, ProtocolIO::Reply(reply::error(&format!("unknown message: {}", text))));
            }
        }
        if to_heartbeat {
            on_message_heartbeat(cid);
        }
        Ok(())
    }
}

fn alloc_client(tx:Sender<ProtocolIO>) -> ClientId {
    for i in 0..CLIENT_MAX_NUMS {
        if ! instance_is_alive(i) {
            let time_htake = Instant::now();
            let time_hreply = time_htake.clone();
            let _ = SHARED.instances.set(i, Instance {
                tx, time_htake, time_hreply,
            });
            return i
        }
    }
    CLIENT_MAX_NUMS
}
pub fn alloc(tx:Sender<ProtocolIO>) -> Result<ClientId> {
    let cid = alloc_client(tx);
    if cid >= CLIENT_MAX_NUMS {
        Err(anyhow!("over max instance nums: {}", CLIENT_MAX_NUMS))
    } else {
        if let Err(e) = engine::alloc(cid) {
            free(cid);
            log::error!("[protocol|client:{}] can't alloc engine: {}", cid, e);
            Err(e)
        } else {
            log::info!("[protocol|client:{}] has alloc instance", cid);
            Ok(cid)
        }
    }
}
pub fn free(cid:ClientId) {
    debug_assert!(cid < CLIENT_MAX_NUMS);
    if instance_is_alive(cid) {
        let _ = SHARED.instances.remove(cid);
        engine::free(cid);
        log::info!("[protocol|client:{}] has free instance", cid);
    }
}
pub fn end() {
    SHARED.running.store(false, Ordering::Relaxed);
}
